Skip to content
0

Docker - Dockerfile

什么是 Dockerfile

Dockerfile 可以认为是 Docker 镜像的描述文件,是由一系列命令和参数构成的脚本。主要作用是用来构建 docker 镜像的构建文件。

更直接的理解:Dockerfile 就是一个文件的名字,如 Java 文件叫 xxx.java;Dockerfile 文件就叫 Dockerfile

image-20211121221858094

解析过程

image-20211121225615709

基本知识

  • 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  • 指令按照从上到下,顺序执行
  • # 表示注释
  • 每条指令都会创建一个新的镜像层,并对镜像进行提交

大致流程

Dockerfile 是用来构建 docker 容器的,它是由一系列的指令和参数构成,通过 build dockerfile 可以得到镜像。Dockerfile 的每一个保留字指令 必须大写,后面至少跟一个参数,它的解析过程:首先拉取基础镜像,当执行一条指令的时候,基于基础镜像运行容器,对容器进行修改,然后 commit 新的镜像层;当再次执行指令的时候,基于新镜像运行容器,对容器进行修改 commit 新的镜像层,这样反复产生镜像(「不完整镜像」),最终只会获得一个完整的镜像。而原先产生的「不完整镜像」存入 Cache(缓存)里,如果再次构建镜像,则不需要从头开始,直接从缓存里找到需要的「不完整镜像」。

Dockerfile、镜像、容器的理解

从应用软件的角度来看,Dockerfile、Docker 镜像、Docker 容器分别代表着三个不同的阶段:

  • Dockerfile 是软件的基本代码
  • Docker 镜像是软件的交付品
  • Docker 容器可以认为是软件的运行态
  • Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器负责运维和部署,三者缺一不可

指令表

官方文档:https://docs.docker.com/engine/reference/builder/

关键字说明小写
FROM基础镜像,当前新镜像是基于哪个镜像的。第一个指令必须是 FROMfrom
MAINTAINER镜像维护者的姓名和邮箱地址(废弃)maintainer
RUN构建容器时需要运行的命令run
EXPOSE当前容器对外暴露出的端口expose
WORKDIR指定在创建容器后,终端默认登录的进来工作目录,一个落脚点workdir
ENV用来在构建镜像过程中设置环境变量env
ADD将宿主机目录下的文件拷贝进镜像且 ADD 命令会自动处理 URL 和解压 tar 压缩包add
COPY类似 ADD,拷贝文件和目录到镜像中
将从构建上下文目录中<原路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置
copy
VOLUME容器数据卷,用于数据保存和持久化工作volume
CMD指定一个容器启动时要运行的命令
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
cmd
ENTRYPOINT指定一个容器启动时要运行的命令
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及其参数
entrypoint
ONBUILD当构建一个被继承的 DockerFile 时运行命令,父镜像在被子镜像继承后,父镜像的 ONBUILD 被触发onbuild

环境变量

在 Dockerfile 中,环境变量是以 $variable_name${variable_name} 表示,它们被同等对待,并且大括号通常用于解决没有空格的变量名称的问题。

${variable_name} 语法还支持 bash 以下指定的一些标准修饰符:

  • ${variable:-word} 表示如果 variable 设置,则结果将是该值。如果 variable 未设置,word 则将是结果。
  • ${variable:+word} 表示如果 variable 设置则为 word 结果,否则为空字符串。

在所有情况下,word 可以是任何字符串,包括额外的环境变量。

如果只是单纯使用 $ 而不是作为变量使用,请加上 \ 进行转义。例如:\$foo\${foo},将分别转换为 $foo${foo} 文字。

指令使用

NOTE

本内容使用 centos 7 版本的镜像进行测试。

顺便一提的是,上下文指的是 Dockerfile 文件所在的目录(根目录),请把指令操作的根目录当作上下文的源。

2021-11-22 @Young Kbt

sh
# 拉取centos 7 的镜像命令
docker pull centos:centos7

# 查看镜像
docker images

# 返回结果
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
tomcat        8.5.73    7ec084df520c   3 days ago     249MB
mysql         latest    b05128b000dd   4 days ago     516MB
hello-world   latest    feb5d9fea6a5   8 weeks ago    13.3kB
centos        7         eeb6ee3f44bd   2 months ago   204MB

build 指令

这是 docker 的指令,并非是 Dockerfile 的指令,但是和 Dockerfile 关系很大。

这是将 Dockerfile 打包成镜像的指令。

语法:

sh
# 语法格式
docker build [options] <自定义镜像名>[:tag] <Dockerfile 路径>

# 先指定 tag 版本,再指定 Dockerfile 所在路径
docker build -t <自定义镜像名:tag> <Dockerfile>

# 先指定 Dockerfile 所在路径,再指定 tag 版本
docker build -f <Dockerfile 的路> -t <自定义镜像名:tag>

常用的是第 5 行的打包方式。

例子 1:直接打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele

sh
docker build -t kele:1.0 /opt

不需要写到 Dockerfile 文件名,它默认会找到这个文件

例子 2:利用 -f 打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele

sh
docker build -f /opt/Dockerfile -t kele:1.0 .

FROM 指令

基于那个镜像进行构建新的镜像,在构建时会自动从 docker hub 拉取 base 镜像或者本地拉去镜像(优先本地)。必须作为 Dockerfile 的第一个指令出现,该指令可以出现多次,但是仍以最后一条 FROM 为准。

语法:

dockerfile
FROM <image> [AS <name>]    # 默认版本是 latest
FROM <image>[:<tag>] [AS <name>]    # 当版本不写 latest 时,请填写其他版本
FROM <image>[@<digest>] [AS <name>]  # 使用摘要

例子 1:创建 Dockerfile 文件,引用 centos 7

  • 首先在 /opt 下创建 docker-file 目录,专门来做测试

    sh
    cd /opt
    
    mkdir docker-file
    
    cd docker-file
  • 创建 Dockerfile 文件,并给文件添加 FROM 指令

    dockerfile
    vim Dockerfile
    
    # 此时已经进入 Dockerfile 文件
    # 添加内容
    FROM centos:7
  • 执行 build 指令,查看结果

    sh
    # 执行打包命令
    docker build -t mycentos7:1.0 .
    
    # 查看 centos 镜像
    docker images mycentos7
    
    # 返回结果
    REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
    mycentos7     1.0       eeb6ee3f44bd   2 months ago   204MB

    . 代表当前目录。

MAINTAINER 指令

镜像维护者的姓名和邮箱地址(废弃)

语法:

dockerfile
MAINTAINER <name>

RUN 指令

RUN 指令将在当前映像之上的新层中执行任何命令并提交结果。生成的提交映像将用于 Dockerfile 中的下一步。

语法:(两种形式)

dockerfile
# shell 形式,命令在 shell 中运行,默认 /bin/sh -c 在 Linux 或cmd /S /CWindows 上
RUN <command>
# 如:
RUN echo hello

# 执行形式
RUN ["参数1", "参数2", "参数3", .....]
RUN ["executable", "param1","param2 "]

# 例子:
RUN /bin/bash -c echo hello
# 等价于
RUN["/bin/bash", "-c", "echo hello"]

在 shell 形式中,您可以使用 \(反斜杠)将单个 RUN 指令延续到下一行。例如,考虑以下两行:

dockerfile
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

它们一起相当于这一行:

dockerfile
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

要使用除 /bin/sh 之外的不同 shell,请使用传入所需 shell 的 exec 形式。例如:

RUN["/bin/bash", "-c", "echo hello"]

重复 RUN 的指令会进入缓存里,下次再 RUN 该指令,直接去缓存获取结果。可以使用 --no-cache 标志使指令缓存无效,例如

dockerfile
docker build --no-cach <指令>

例子 1:给 centos 安装 vim 指令

  • 官方的 CentOS 并没有安装 vim 指令,进入官方的 CentOS 容器,执行 vim 指令,会报错

    sh
    # 启动 centos 7
    docker run -it centos:7
    # 使用 vim 指令
    [root@438f1c67afb1 /]# vim aa.txt
    
    # 没有安装
    bash: vim: command not found
  • 我们在打包成镜像的时候,让其自动安装,在 Dockerfile 文件添加 RUN 指令

    有两种形式,我们使用任意一个即可(这是 Dockerfile 文件

    dockerfile
    FROM centos:7
    
    RUN yum install -y vim
    # 另一种格式
    # RUN ["yum","install","-y","vim"]
  • 打包镜像并验证结果

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 验证结果
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 使用 vim 指令
    [root@438f1c67afb1 /]# vim aa.txt
    
    # 直接创建 aa.txt 文件并进入

    build 后的镜像如果在本地已经有了,则会覆盖本地的镜像

NOTE

第一次执行 RUN 指令,会很慢,但是下一次重复执行该指令,就很快了,因为有缓存。

什么时候又变慢呢?RUN 指令发生了修改,那么缓存里旧的指令就被删除,存入新的指令。

2021-11-22 @Young Kbt

EXPOSE 指令

用来指定构建的镜像在运行为容器时 对外暴露端口

语法:

dockerfile
EXPOSE 端口号/类型   # 如果没有指定类型,则默认暴露都是 tcp

EXPOSE 指令 实际上并未发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于打算发布哪些端口。要在运行容器时实际发布端口,请使用 -p 指令在 docker run 时来发布和映射一个或多个端口,或者使用 -P 指令来发布所有暴露的端口并将它们映射到高阶端口。

默认情况下,EXPOSE 指定 TCP。您还可以指定 UDP。

dockerfile
EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果您使用 -P,则端口将为 TCP 公开一次,为 UDP 公开一次。请记住,-P 在主机上使用临时高阶主机端口,因此 TCP 和 UDP 的端口将不同。

无论 EXPOSE 设置如何,您都可以在运行时使用 -p 标志覆盖它们。例如:

sh
docker run -p 80:80/tcp -p 80:80/udp ...

例子 1:暴露 8080 和 9090 端口

  • 在 Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    EXPOSE 8080
    EXPOSE 9090/tcp

WORKDIR 指令

用来为 Dockerfle 中的任何 RUN、CMD、ENTPYPOINT、COPY 和 ADO 指令设置工作目录。如果 WORKDIR 不存在,即使它没有在任何后续 Docerile 指令中使用,它也将被创建。一旦创建了 WORKDIR 目录,启动容器并进入容器时,默认处于该目录下。

语法:

dockerfile
WORKDIR <绝对路径> | <相对路径>

注意:WORKDIR 指令可以在 Dockerfile 中多次使用。如果提供了相对路径,它将相对于前一条 WORKDIR 指令的路径。例如:

dockerfile
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终 pwd 命令的输出 Dockerfile 将是 /a/b/c

WORKDIR 指令可以解析先前使用 ENV。你只能使用在 Dockerfile。例如:

dockerfile
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

最终 pwd 命令的输出 Dockerfile 将是 /path/$DIRNAME

例子 1:添加工作目录 /data/kele

  • Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    WORKDIR /data
    WORKDIR kele
  • WORKDIR 指定的目录,就是进入容器后所处的目录

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# pwd
    
    # 返回结果
    /data/kele

ENV 指令

用来为构建镜像设置环境变量。这个值将出现在构建阶段中所有后续指令的环境中。

语法:

dockerfile
ENV <key> <value>
# 或者
ENV <key>=<value

# 例子:
ENV BASH_PATH /data/kele
# 使用 BASH_PATH 就是使用 /data/kele 目录

这个指令类似于 Java 的数据类型。

什么时候用呢?当 Dockerfile 文件里某一字符串出现率太高,可以将字符串赋值给一个变量,引用该变量即可(引用变量使用 $ 作为前缀)。

  • ENV 指令 允许 <key>=<value> ... 一次设置多个变量,例如:

    dockerfile
    ENV MY_NAME="John Doe"
    ENV MY_DOG=Rex\ The\ Dog
    ENV MY_CAT=fluffy

    可以写成:

    dockerfile
    ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
        MY_CAT=fluffy

    ENV 当容器从生成的映像运行时,使用设置的环境变量将持续存在。您可以使用来查看值 docker inspect,并使用 更改它们 docker run --env <key>=<value>

  • ENV 指令 不允许 <key> <value> ...一次设置多个变量,例如:

    dockerfile
    ENV ONE TWO= THREE=world

例子 1:创建一个变量,代表 /data/kele 目录

  • Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# pwd
    
    # 返回结果
    /data/kele

ADD 指令

NOTE

使用该指令前,要明白,该指令只针对 Dockerfile 文件所在的目录下,也称为 上下文的源目录

2021-11-22 @Young Kbt

用来从 context 上下文复制新文件、目录或远程文件 url,并将它们添加到位于指定路径的映像文件系统中。

两种形式:

dockerfile
# Linux 下可选择用户和组
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

包含空格的路径需要后一种形式。

NOTE

--chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。

2021-11-22 @Young Kbt

<src> 可以指定多个资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建上下文的源(即 Dockerfile 所在的目录下)。

每个都 <src> 可能包含通配符,匹配将使用 Go 的 filepath.Match 规则完成。也有很多规则。例如:

  • 添加所有以 "hom" 开头的文件

    dockerfile
    ADD home* /mydir/  # 通配符添加多个文件
  • ? 可以被替换为任何单个字符

    dockerfile
    ADD hom?.txt /mydir/  # 通配符添加
  • <dest> 是一个绝对路径,或相对于一个路径 WORKDIR,到其中的源将在目标容器内进行复制。

    dockerfile
    ADD test.txt relativeDir/  # 可以指定相对路径
  • 使用绝对路径,并将 "test.txt" 添加到 /absoluteDir/

    dockerfile
    ADD test.txt /absolutionDir/  # 可以指定绝对路径
  • 复制文件的时候,<dest>没有出现一个以上的 /,则是对该文件重命名

    dockerfile
    ADD demo-0.0.1-SNAPSHOT.jar app.jar
  • 添加包含特殊字符(如[])的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式。例如,要添加名为 的文件arr[0].txt,请使用以下命令:

    dockerfile
    ADD arr[[]0].txt /mydir/
  • 如果 <src> 是 URL 并且 <dest> 不以斜杠结尾,则从 URL 下载文件并将其复制到 <dest>

    dockerfile
    ADD url <dest>  # 远程文件 url 拉取

    如果 <src> 是 URL 并且 <dest> 确实以斜杠结尾,则从 URL 推断文件名并将文件下载到 <dest>/<filename>. 例如,ADD http://example.com/foobar/ 将创建文件 /foobar。URL 必须有一个重要的路径,以便在这种情况下可以发现适当的文件名(http://example.com 将不起作用)。

  • 如果 <src> 是采用可识别压缩格式(identity、gzip、bzip2 或 xz)的本地 tar 存档,则将其解压缩为目录。来自远程 URL 的资源 不会被 解压缩

    dockerfile
    ADD xxx.tar <dest>

    文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称。例如,如果一个空文件恰好以 .tar.gz 结尾,则不会将其识别为压缩文件,也不会 生成任何类型的解压缩错误消息,而只会将该文件复制到目的地。

  • 如果 <src> 是目录,则复制目录的全部内容,包括文件系统元数据,但是不会复制目录本身。

    dockerfile
    ADD /opt /mydir/

其他规则:

  • 如果 <src> 是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果 <dest> 以斜杠结尾/,它将被视为一个目录,其内容 <src> 将被写入 <dest>/base(<src>)
  • 如果 <src> 直接指定了多个资源,或者由于使用了通配符,则 <dest> 必须是目录,并且必须以斜杠结尾 /
  • 如果 <dest> 不以斜杠结尾,则将其视为常规文件,并将其内容 <src> 写入 <dest>.
  • 如果 <dest> 不存在,则创建它及其路径中所有丢失的目录。

只能在 Dockerfile 所处的目录内进行复制,也就是说 ADD /opt/test.txt /data/kiele 的 test.txt 文件在 Dockerfile 所处路径的 opt 目录里,并不是相对于操作系统的目录而言。

例子 1:添加一个文件到容器里的 /data/kele 目录下

  • 首先在 Dockerfile 目录里创建一个文件 aa.txt

    sh
     # 进入 Dockerfile 目录里
     cd /opt/docker-file
    
     # 添加 aa.txt 文件
     touch aa.txt
  • Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD aa.txt /data/kele
    # 或者
    # RUN mv a.txt /data/kele
  • 验证结果

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    aa.txt

例子 2:添加一个压缩包到容器里的 /data/kele 目录下

  • 首先创建一个压缩包

    sh
     # 进入 Dockerfile 目录里
     cd /opt/docker-file
    
     # 添加 aa.txt 文件
     touch bb.txt
    
     # 创建压缩包
     tar cvf bb.txt.tar bb.txt
  • Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD bb.txt.tar /datakele
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    bb.txt

COPY 指令

用来将 context 目录中指定文件复制到镜像的指定目录中。

有两种形式:

dockerfile
# Linux 下选择用户和组
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

NOTE

--chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。

2021-11-22 @Young Kbt

它是 ADD 的缩小版,功能不那么冗余,有一个区别就是:不支持解压

例子 1:添加一个压缩包到容器里的 /data/kele 目录下

  • Dockerfile 文件添加内容

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD aa.txt /data/kele
    
    COPY bb.txt.tar /data/kele
  • 验证结果

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    aa.txt bb.txt.tar

    说明复制的压缩包不会自动解压。

VOLUME 指令

VOLUME 指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷。该值可以是 JSON 数组、VOLUME ["/var/log/"] 或带有多个参数的纯字符串,例如 VOLUME /var/logVOLUME /var/log /var/db

语法:

dockerfile
VOLUME <容器内>

# 例子:
VOLUME /myvol/greeting
# 或者
VOLUME ["myvol", "greeting"]

缺点:主机目录(挂载点)本质上是依赖于主机的。这是为了保持图像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从 Dockerfile 中挂载主机目录。该 VOLUME 指令不支持指定 host-dir 参数。您必须在创建或运行容器时指定挂载点。

也就是说,VOLUME 指令使用的是匿名目录挂载,只能指定容器挂载目录,而在宿主机的挂载目录名是一大长串 ID。

例子 1:挂载 /myvol 目录

  • Dockerfile 文件添加内容:

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 myvol]# ls
    # 返回结果
    greeting
    
    # 查看文件
    vim greeting
    # 返回结果
    "hello docker"

    查看宿主机的挂载目录在哪里?

    sh
    find / -name greeting
    
    # 返回结果
    var/lib/docker/volumes/a9ab2b428361cbdc60beafadb585fb7312bec2fbcb9971c1806a7f0a60ef4de3/_data/greeting

ENTRYPOINT 和 CMD 指令

两个命令都是指定一个容器启动时要运行的命令。

  • ENTRYPOINT 指令,往往用于设置容器启动后的 第一个命令,这对一个容器来说往往是固定的

    docker run 的参数会被当做参数传递给 ENTRYPOINT,形成新的命令组合。

  • CMD 指令,往往用于设置容器启动的第一个命令的 默认参数,这对一个容器来说可以是变化的

    可以有多个 CMD 指令,但只有最后一个生效。

ENTRYPOINT 和 CMD 的两种形式:

  • JSON 数组形式,命令组合时必须用的形式:

    dockerfile
    ENTRYPOINT ["executable", "param1", "param2"]
    CMD ["executable", "param1", "param2"]
  • 标准格式:

    dockerfile
    ENTRYPOINT command param1 param2
    CMD command param1 param2

ENTRYPOINT 的最大价值是和 CMD 组合使用,实现传参效果,下面我们开始举三个例子,第一、二例子分别使用两个指令,第三个例子是指令组合使用。

例子 1:使用 CMD 命令

  • Dockerfile 文件添加内容:

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    CMD ls /data/kele
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol

相比较 ENTRYPOINT,CMD 有一个很大的区别:可以在 docker run 的后面使用参数覆盖掉 CMD 指令

  • Dockerfile 文件添加内容:

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    CMD ls /data/kele
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0 ls /data
    
    # 返回结果
    kele

    第 5 行代码,在启动的时候使用 ls /data 覆盖了 Dockerfile 文件的 ls /data/kele 命令,如果启动的时候使用 pwd 也会覆盖掉 ls /data/kele

    sh
    # 启动镜像并进入
    docker run -it mycentos7:1.0 pwd
    
    # 返回结果
    /data/kele/myvol

    证明了 CMD 是可以被外界命令覆盖的。

例子 2:使用 ENTRYPOINT 命令

  • Dockerfile 文件添加内容:

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    ENTRYPOINT ls /data/kele
  • 验证结果:

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol

ENTRYPOINT 指令可以被覆盖吗?

是可以的。它和 CMD 的区别就在于不能被 直接覆盖,但是在 docker run 后使用 --entrypoint 就可以实现覆盖。

使用 pwd 覆盖 Dockerfile 文件的指令:

sh
# # 启动镜像
docker run -it --entrypoint pwd mycentos7:1.0

# 返回结果
/data/kele/myvol

但是 --entrypoint 放在镜像名后面是不行的:

sh
# # 启动镜像
docker run -it mycentos7:1.0 --entrypoint pwd

# 返回结果
aa.txt	bb.txt	myvol

例子 3:组合指令使用

ENTRYPOINT 的指令无法被直接覆盖,可以作为 默认指令,CMD 的指令可以被直接覆盖,可以作为 默认参数

组合指令需要用到的的形式必须是 JSON 数组形式:

dockerfile
ENTRYPOINT ["executable", "param1", "param2"]
CMD ["executable", "param1", "param2"]
  • Dockerfile 文件添加内容:

    dockerfile
    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    ENTRYPOINT ["ls"]
    CMD ["/data/kele"]
  • 打包镜像

    sh
    # 打包成镜像
    docker build -t mycentos7:1.0 .
  • 不传参启动

    sh
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol
  • 传参启动

    sh
    # 带参数启动
    docker run -it mycentos7:1.0 /data
    
    # 返回结果
    kele

    说明 /data 已经覆盖了 CMD 的默认值 /data/kele实现了传参功能。

    但是因为默认指令是 ls,如果想要其他指令,要么使用 --entrypoint 进行覆盖,要么修改 Dockerfile 文件的 ENTRYPOINT 指令。

启动 Java 的 jar 包可以写出这样:

dockerfile
FROM centos:7

......

ENTRYPOINT ["java","-jar"]
CMD ["默认 jar 包"]

先指定一个默认 jar 包。如果后期修改了 jar 包,直接在 docker run 后面加入新的 jar 包名即可。

ARG 指令

ENTRYPOINT 和 CMD 组合指令只能在 docker run 的时候传参。那有没有想过,想在 docker build 的时候进行传参,把参数传入到 Dockerfile 的某个变量里,这样打包出来的镜像塑造性高。

ARG 指令可以实现这个功能。

dockerfile
ARG <name>[=<default value>]

ARG 指令定义了一个变量,用户可以在构建时 docker build 通过使用 --build-arg <varname>=<value> 标志的命令将其传递给构建器。如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告。

sh
# 输出警告
[Warning] One Or more build-args [foo] were not consumed

一个 Dockerfile 可能包含一个或多个 ARG 指令。例如,以下是一个有效的 Dockerfile:

dockerfile
FROM centos:7
ARG user1
ARG buildno
# ......

危险

不建议使用构建时变量来传递秘密,如 github 密钥、用户凭据等信息,因为可以通过 docker history 等查看历史记录。

2021-11-22 @Young Kbt

默认值

一条 ARG 指令可以选择包含一个默认值:

dockerfile
FROM centos:7
ARG user1=someuser
ARG buildno=1
# ......

ARG 变量定义从 Dockerfile 中定义它的行开始生效,而不是从命令行或其他地方使用参数开始生效。例如,考虑这个 Dockerfile:

sh
FROM centos:7
WORKDIR ${path:/data}
ARG path
WORKDIR $path
# ......

通过调用构建此文件传参:

sh
docker build --build-arg path=/data/kele ./

第 2 行的工作目录使用了 path 变量,并指定初始值为 /data,但是该变量在随后的第 3 行中才开始定义。所以第 4 行的 path 不是 /data,而是构建时传进来的 /data/kele。在 ARG 指令定义变量之前,任何变量的使用都会导致空字符串。

ARG 指令在构建阶段(build)结束后失效。要在多个阶段中使用 arg,每个阶段必须包含 arg 指令。

dockerfile
FROM centos:7
ARG SETTINGS
RUN ./run/setup $SETTINGS

# 必须重新定义 SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

使用 ARG 变量

其实看到这,很多人都会想到 ARG 不就是 ENV 吗,都是定义一个变量,然而官方不可能这么做,肯定有所区别,那么接下来请仔细阅读下面内容。

使用 ENV 指令定义的环境变量 总是覆盖 ARG 同名指令

dockerfile
FROM centos:7
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER

在构建时,传入参数:

sh
 docker build --build-arg CONT_IMG_VER=v2.0.1 .

在这种情况下,RUN 指令使用 v1.0.0 而不是 ARG 传递的参数:v2.0.1,此行为类似于 shell 脚本,本地范围的变量覆盖参数传递或环境继承的变量。

如果定义了 ARG 变量,但是又不想在构建阶段传参,可以使用 ENV 进行配合:

dockerfile
FROM centos:7
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER

ARG 指令不同,ENV 值始终保留在构建的映像中,所以 CONT_IMG_VER 仍保留在映像中,CONT_IMG_VER 的默认值是 v1.0.0,哪怕构建阶段不传参数,也不会报错。

预定义的 ARG

Docker 有一组预定义的 ARG 变量,您可以 ARG 在 Dockerfile 中没有相应指令的情况下使用这些变量。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

要使用这些,请使用 --build-arg 标志在命令行上传递它们,例如:

sh
docker build --build-arg HTTPS_PROXY=https://my-proxy.example.com .

默认情况下,这些预定义变量的值存入 docker history. 所以尽量不要使用这些变量存入敏感的身份验证信息等。

例如,考虑使用以下 Dockerfile 构建 --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

FROM centos:7
RUN echo "Hello World"

在这种情况下,因为文件里没有 HTTP_PROXY 变量,所以 docker history 不会缓存该变量的信息。

ARG 和 ENV 区别

  • 在 Dockerfile 中使用,仅在 build docker image 的过程中(包括 CMD 和 ENTRYPOINT)有效,在 image 被创建和 container 启动之后,无效。如果你在 Dockerfile 中使用了 ARG 但并未给定初始值,则在运行 docker build(编译)的时候未指定该 ARG 变量,则会失败。虽然其在 container 启动后不再生效,但是使用 docker history 可以查看到。所以,敏感数据不建议使用 ARG.

.dockerignore 文件

在 docker CLI 将上下文发送到 docker 守护进程之前,它会在上下文的根目录中查找名为 .dockerginore 的文件。如果此文件存在,CLI 将修改上下文以排除与其中模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护进程,并可能使用 ADDCOPY 将它们添加到图像中。

CLI 将 .dockerignore 文件解释为以换行符分隔的模式列表,类似于 Unix shell 的文件 glob。出于匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar 都排除 PATH 的 foo 子目录或位于 URL 的 git 存储库根目录中名为 bar 的文件或目录。两者都不排除任何其他因素。

如果 .dockerignore 文件中的一行在第 1 列中以 # 开头,则该行被视为注释,并在被 CLI 解析之前被忽略。

这是一个示例 .dockerignore 文件:

# comment
*/temp*
*/*/temp*
temp?

此文件会导致以下构建行为:

规则行为
# comment忽略。
*/temp*排除根目录的任何子目录中名称以 temp 开头的文件和目录。例如,纯文件 /somedir/temporary.txt 被排除在外,目录 /somedir/temp 也被排除在外
*/*/temp*从根目录下两级的任何子目录中排除以 temp 开头的文件和目录。例如,不包括 /somedir/subdir/temporary.txt
temp?排除根目录中名称为 temp 的单字符扩展名的文件和目录。例如,不包括/tempa/tempb

使用 Go 的 filepath.Match 规则完成匹配。预处理步骤将删除前导和尾随空格并消除。预处理后为空的行将被忽略。

除了 Go 的 filepath.Match 规则之外,Docker 还支持一个特殊的通配符字符串 ** 来匹配任意数量的目录(包括零)。例如,**/*.go 将排除(包括生成上下文的根目录)中以 .go 结尾的所有文件

!(感叹号)开头的行可用于排除例外。以下是 .dockerignore 使用此机制的示例文件:

*.md
!README.md

除 README.md 之外的所有标记文件都从上下文中排除

!异常规则的位置会影响行为:.dockerignore 匹配特定文件的最后一行决定是包含还是排除它。考虑以下示例:

*.md
!README*.md
README-secret.md

除了 README 文件之外,上下文中不包含任何文件 README-secret.md

现在考虑这个例子:

*.md
README-secret.md
!README*.md

包含所有 README 文件。中间的线没有效果,因为 !README*.mdREADME-secret.md 匹配,并且排在最后。

您甚至可以使用该 .dockerignore 文件来排除 Dockerfile.dockerignore 文件。这些文件仍然被发送到守护进程,因为它需要它们来完成它的工作。但是 ADDCOPY 不会将它们复制到镜像中。

最后,您可能希望指定要在上下文中包含哪些文件,而不是要排除哪些文件。为此,请指定 * 为第一个模式,然后是一个或多个 ! 异常模式。

指令实战

CentOS 安装指令

官方默认的 CentOS 的情况不支持 vimifconfig 指令

我们自己构建一个支持这些指令的镜像

1、编写文件

sh
FROM centos:7

ENV PATH /usr/local
WORKDIR $MYPATH

RUN yum -y install vim
RUN yum -y install net-tools

2、构建镜像

命令最后有一个 . 表示当前目录

sh
docker build -t mycentos7:1.0 .

3、运行镜像

sh
docker run -it mycentos7:1.0

4、进入容器使用指令

sh
# 使用 vim 指令
[root@438f1c67afb1 /]# vim aa.txt

# 直接创建 aa.txt 文件并进入

# 使用 ifconfig
ifconfig

测试后,可以看到,我们自己的新镜像已经支持 vim 和 ifconfig 的命令了。

列出镜像的变更历史指令:docker history <镜像名 | 镜像id>

sh
docker history <镜像名 | 镜像id>

CentOS 安装 Tomcat

先准备好 jdk 和 Tomcat,这里是 jdk-8u11apache-tomcat-9.0.46

1、编写文件

Dockerfile 文件内容:

dockerfile
FROM centos:7

# 把 java 与 tomcat 添加到容器中
ADD jdk-8u11-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.46.tar.gz /usr/local/

# 安装 vim 编辑器
RUN yum -y install vim

# 变量
ENV BASE_PATH /usr/local

# 设置工作目录,即进入容器后默认所在目录
WORKDIR $BASE_PATH

# 配置 Java 与 tomcat 的环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.46
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.46
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

# 容器运行时监听的端口
EXPOSE 8080

# 启动时运行 tomcat
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.46/bin/startup.sh" ]
# CMD ["/usr/local/apache-tomcat-9.0.46/bin/catalina.sh","run"]
# 或者
CMD /usr/local/apache-tomcat-9.0.46/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.46/bin/logs/catalina.out

2、构建镜像

sh
docker build -t mytomcat:1.0 .

3、运行镜像

sh
docker run -d -p 8080:8080 --name mytomcat -v tomcat_test:/usr/local/apache-tomcat-9.0.46/webapps/test -v tomcat_logs:/usr/local/apache-tomcat-9.0.46/logs --privileged=true mytomcat:1.0

如果出现:cannot open directory Permission denied

在挂载目录后面加上 --privileged=true 参数即可。

4、测试

直接在宿主机的 tomcat_test 目录新建一个 test.jsp 文件

sh
cd /var/lib/docker/volumes/tomcat_test/_data

vim test.jsp

随便加入内容:

html
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Insert title here</title>
  </head>
  <body>
    <h2>Hello Docker!</h2>
  </body>
</html>

打开浏览器访问:http://192.168.199.27:8080/test/test.jsp

IP 地址根据自己的情况填写。

SpingBoot

1、使用 IDEA 构建一个 SpringBoot 项目

2、编写 Controller

java
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
	    return "Hello Docker";
    }
}

利用 Maven 打成 jar 包。

3、编写文件

将打包好的 jar 包拷贝到 Dockerfile 同级目录,然后开始编写 Dockerfile 文件

dockerfile
FROM java:8

# 拷贝 jar 包到镜像里,并改名为 app.jar
COPY *.jar /app.jar

# 启动镜像执行的命令
CMD ["--server.port=8080"]

# 指定容器内要暴露的端口
EXPOSE 8080

# 启动镜像后,启动 jar 包
ENTRYPOINT ["java","-jar"]
CMD ["/app.jar"]

4、构建镜像

sh
# 构建镜像
docker build -t test-jar:1.0 .

# 运行
docker run -d -P --name test-jar test-jar:1.0

5、测试

打开浏览器访问:http://192.168.199.27:8080/hello

IP 地址根据自己的情况填写。

最近更新